Skip to main content

Adapter Design Pattern

  • It is a structural design pattern that allows incompatible interfaces to work together by the converting the interfaces into one class that client expects.

Useful in situations

  • where we have to integrate with the legacy system or third party library which doesn't match current interface.
  • Want to make unrelated classes work together

Example without using adapter

  • I have a payment system which currently only support legacy Bank Transfer but i want to implement modern stripe so
  • first soln can be with writing conditional logic based on type but it further violate Open/Closed principle and make code tightly coupled.
  • Current interfaces look like this
public interface PaymentProcessor {
void processPayment(double amount,String currency);
bool isPaymentSuccess();
String getTransactionId();
}
  • and have a Checkout service which accept payment processor
public class CheckoutService {
private PaymentProcessor processor;

... further it used PaymentProcessor method to complete payment and check status
}
  • Now we get requirement of integrating a legacy method for which interface like
public interface LegacyProcessor {
void executePayment(double amount,String currency);
String getStatus(long tid);
long getPaymentRefrence();
}
  • So if we want to serve with LegacyProcessor as well and we have some constraint
    • Can't change CheckoutService as it is used at lot of other place
    • Can't modify legacy code
    • both option should work together

Adapter Pattern

  • To solve above issue adapter pattern comes into picture

Class Diagram

  • Target Interface (PaymentProcessor) - The interface that client excpects and use.
  • Adapter - The class that implements the target interface and uses adaptee internally. it convert calls to target interface to call to adaptee interface.
  • Adaptee (LegacyProcessor) - the existing class with an incompatible interface that needs adapting.
  • Client (CheckoutService) - part of systme that uses the target interface.

Implementation

  • Below is adapter implementation which implement PaymentProcessor (Target Interface) so that it use functions of LegacyProcessor(Adaptee).
public class LegacyProcessorAdapter implements PaymentProcessor {
private final LegacyProcessor legacyProcessor;
private long ref;

public LegacyProcessorAdapter(LegacyProcessor legacyp){
this.legacyProcessor = legacyp;
}

@Override
void processPayment(double amount,String currency){
legacyProcessor.executePayment(amount,curreny); // fn of diff interface
ref = legacyProcessor.getPaymentRefrence();
}

@Override
bool isPaymentSuccess(){
return legacyProcessor.getStatus(re) == "SUCCESS";
}

@Override
String getTransactionId(){
return "LEGACY_" + ref;
}
}
  • By this way our client code will be unchanged.
public class MainApp {
psvm(String[] args){

// With Existing processor
PaymentProcessor processor = new PaymentProcessor();
CheckoutService checkout = new CheckoutService(processor);

// With new legacy processor
LegacyProcessor processor = new LegacyProcessor();
processor = new LegacyProcessorAdapter(processor);
CheckoutService legacyCheckout = new CheckoutService(processor);

}
}

What it actually does?

Composition

  • the adapter wraps the legacy code instead of subclassing it. which benefit in
    • loose coupling
    • Flexible to change

Method Transalation

  • Each method of PaymentProcessor is trnaslated into the equivalents call to the legacy API. this includes
    • renaming or remaping method name and return types
    • reorg parameters